using System;
using System.Collections.Generic;
using System.Data;
using AD = System.DirectoryServices.ActiveDirectory;
using ActiveDirectoryUtilities.DirectoryServiceAccess;
using System.DirectoryServices.Protocols;
using System.Text;

namespace ActiveDirectoryUtilities.ReplDiag
{
	class ProcessReplicationInformation
	{
		#region Constants
		public const string COLUMN_NAME_FAIL_TYPE = "Type of Failure";
		public const string COLUMN_NAME_DESTINATION_DC = "Destination DC";
		public const string COLUMN_NAME_SITE_NAME = "Site";
		public const string COLUMN_NAME_NAMING_CONTEXT = "Naming Context";
		public const string COLUMN_NAME_IS_WRITABLE = "Is Writable";
		public const string COLUMN_NAME_SOURCE_DC = "Source DC";
		public const string COLUMN_NAME_LAST_SUCCESSFUL_SYNC = "Last Successful Sync";
		public const string COLUMN_NAME_NUMBER_OF_SYNC_FAILURE_ATTEMPTS = "NumFailures";
		public const string COLUMN_NAME_ERROR_CODE = "Error Code";
		public const string COLUMN_NAME_ERROR_MESSAGE = "Error Message";
		public const string COLUMN_NAME_IS_CRITICAL = "Is Critical";
		#endregion

		private List<DomainControllerReplicationInfo> _dcs;
		private Dictionary<string, int> _ncInstanceCount = new Dictionary<string, int>();
		private bool _isReplicationTopologyStable = true, _isReplicationTopologyUnstableDueToLingeringObjects = false;
		private DataTable _replicationTopologyStateData = null;

		public DataTable ReplicationTopologyStateData { get { return _replicationTopologyStateData; } }

		public bool IsReplicationToplogyStable { get { return _isReplicationTopologyStable; } }
		public bool IsReplicationTopologyUnstableDueToLingeringObjects { get { return _isReplicationTopologyUnstableDueToLingeringObjects; } }

		public ProcessReplicationInformation(List<DomainControllerReplicationInfo> dcsInForest)
		{
			if ((dcsInForest == null) || (dcsInForest.Count == 0))
			{
				throw new ApplicationException("List of DCs in forest is empty");
			}

			_dcs = dcsInForest;

			_replicationTopologyStateData = new DataTable();
			_replicationTopologyStateData.Columns.Add(COLUMN_NAME_FAIL_TYPE, typeof(string));
			_replicationTopologyStateData.Columns.Add(COLUMN_NAME_IS_CRITICAL, typeof(bool));
			_replicationTopologyStateData.Columns.Add(COLUMN_NAME_DESTINATION_DC, typeof(string));
			_replicationTopologyStateData.Columns.Add(COLUMN_NAME_SITE_NAME, typeof(string));
			_replicationTopologyStateData.Columns.Add(COLUMN_NAME_NAMING_CONTEXT, typeof(string));
			_replicationTopologyStateData.Columns.Add(COLUMN_NAME_IS_WRITABLE, typeof(bool));
			_replicationTopologyStateData.Columns.Add(COLUMN_NAME_SOURCE_DC, typeof(string));
			_replicationTopologyStateData.Columns.Add(COLUMN_NAME_LAST_SUCCESSFUL_SYNC, typeof(DateTime));
			_replicationTopologyStateData.Columns.Add(COLUMN_NAME_NUMBER_OF_SYNC_FAILURE_ATTEMPTS, typeof(uint));
			_replicationTopologyStateData.Columns.Add(COLUMN_NAME_ERROR_CODE, typeof(int));
			_replicationTopologyStateData.Columns.Add(COLUMN_NAME_ERROR_MESSAGE, typeof(string));

			analyzeTopology();
		}

		/// <summary>
		/// Query a DC for the replication details
		/// </summary>
		/// <param name="stateInfo">An object of type DomainControllerReplicationInfo</param>
		public static void CollectDCInformation(object stateInfo)
		{

			int startTicks = Environment.TickCount;
			DomainControllerReplicationInfo dcri = (DomainControllerReplicationInfo)stateInfo;
			System.Diagnostics.Debug.WriteLine("Processing {0}.", dcri.DCName);

			if ((dcri.NtDsaGuid == Guid.Empty) || (string.IsNullOrEmpty(dcri.NtdsDsaObjectName)))
			{
				dcri.ErrorMessage = "There is an error in the metadata reported by this server.  Check for sibling name conflicts in the config NC for this server's site.";
				return;
			}

			#region Collect information about what should be on servers.
			try
			{
				using (WLdapSearchRequest wsr = new WLdapSearchRequest(dcri.DCName
						, (int)LDAP_PORTS.LDAP
						, dcri.NtdsDsaObjectName
						, "(objectClass=*)"
						, System.DirectoryServices.Protocols.SearchScope.Base))
				{
					wsr.Attributes.Add("msDS-hasMasterNCs");
					wsr.Attributes.Add("hasMasterNCs");
					wsr.Attributes.Add("hasPartialReplicaNCs");
					SearchResultEntry ntdsSettingsObject = wsr.FindOne();

					//Get Writable NCs
					if (ntdsSettingsObject.Attributes.Contains("msDS-hasMasterNCs"))
					{
						foreach (byte[] ba in ntdsSettingsObject.Attributes["msDS-hasMasterNCs"])
						{
							dcri.AddNC(Encoding.ASCII.GetString(ba), true);
						}
					}
					else if (ntdsSettingsObject.Attributes.Contains("hasMasterNCs"))
					{
						foreach (byte[] ba in ntdsSettingsObject.Attributes["hasMasterNCs"])
						{
							dcri.AddNC(Encoding.ASCII.GetString(ba), true);
						}
					}
					else
					{
						dcri.ErrorMessage = "No writable NCs found in local copy of \"msDS-hasMasterNCs\" or \"hasMasterNCs\".";
						return;
					}

					//Get RO NCs
					if (ntdsSettingsObject.Attributes.Contains("hasPartialReplicaNCs"))
					{
						foreach (byte[] ba in ntdsSettingsObject.Attributes["hasPartialReplicaNCs"])
						{
							dcri.AddNC(Encoding.ASCII.GetString(ba), false);
						}
					}
					//TODO:  Handle RODCs
				}
			}
			catch (LdapException ex)
			{
				dcri.ErrorCode = ex.ErrorCode;
				dcri.ErrorMessage = ex.Message;
				return;
			}
			catch (DirectoryOperationException ex)
			{
				dcri.ErrorMessage = ex.Message;
			}
			finally
			{
				TimeSpan waited = new TimeSpan(0, 0, 0, 0, Environment.TickCount - startTicks);
				System.Diagnostics.Debug.WriteLine("\nProcessed hosted NCs:\t" + dcri.DCName + ":\t" + waited.TotalSeconds.ToString("N0") + "." + waited.Milliseconds.ToString("N0"));
			}
			#endregion

			int midTicks = Environment.TickCount;
			#region Get the replication data as exists on the DC
			AD.DomainController addc = null;
			try
			{
				AD.DirectoryContext dircon = new AD.DirectoryContext(AD.DirectoryContextType.DirectoryServer, dcri.DCName);
				addc = AD.DomainController.GetDomainController(dircon);
				int rncStartTicks = Environment.TickCount;
				AD.ReplicationNeighborCollection rnc = addc.GetAllReplicationNeighbors();
				if ((rnc != null) && (rnc.Count > 0))
				{
					foreach (AD.ReplicationNeighbor rn in rnc)
					{
						if (rn != null)
						{

							if (rn.PartitionName == null)
							{
								throw new ApplicationException("Error processing partition data from " + dcri.DCName);
							}

							string sourceServer = string.Empty;
							//If rn.SourceServer throws a null reference exception, the link is deleted and thus not relevent
							try
							{
								sourceServer = rn.SourceServer;
							}
							catch (NullReferenceException)
							{
								sourceServer = rn.SourceInvocationId.ToString("B");
							}
							if (sourceServer == null)
							{
								Console.WriteLine("Error processing source server on partition " + rn.PartitionName + " on " + dcri.DCName);
								Console.WriteLine("\tThis may be a deleted link waiting the 14 days to be cleaned up.");
								continue;
								//throw new ApplicationException("Error processing source server on partition " + rn.PartitionName + " on " + dcri.DCName); 
							}

							if (rn.LastSuccessfulSync == null)
							{
								throw new ApplicationException("Error processing lastSuccessfulSync data on " + rn.PartitionName + " on " + dcri.DCName + " for " + sourceServer);
							}

							if (rn.ConsecutiveFailureCount == null)
							{
								throw new ApplicationException("Error processing lastSuccessfulSync data on " + rn.PartitionName + " on " + dcri.DCName + " for " + sourceServer);
							}

							if (rn.LastSyncResult == null)
							{
								throw new ApplicationException("Error processing lastSyncResult data on " + rn.PartitionName + " on " + dcri.DCName + " for " + sourceServer);
							}

							if (rn.LastSyncMessage == null)
							{
								throw new ApplicationException("Error processing lastSyncMessage data on " + rn.PartitionName + " on " + dcri.DCName + " for " + sourceServer);
							}

							//only process additional data if the sourceServer data is not populated
							if (!string.IsNullOrEmpty(sourceServer))
							{

#region Commented out since we shouldn't hit null values since now that it was identified that the reason null values were showing up was due to deleted links
//Can be deleted if above code works, left in for testing
/*								string partitionName = string.Empty;
								Guid sourceServerInvocationID = Guid.Empty;
								DateTime lastSuccessfulSync = DateTime.MinValue;
								int consecutiveFailureCount = -1;
								int lastSyncResult = -1;
								string lastSyncMessage = string.Empty;
								try
								{
*/
#endregion
								string partitionName = rn.PartitionName;
								DateTime lastSuccessfulSync = rn.LastSuccessfulSync;
								int consecutiveFailureCount = rn.ConsecutiveFailureCount;
								int lastSyncResult = rn.LastSyncResult;
								string lastSyncMessage = rn.LastSyncMessage.Replace("\n", " ").Replace("\r", "");

								try
								{
									dcri.AddReplicationNeighbor(partitionName
										, sourceServer
										, lastSuccessfulSync
										, consecutiveFailureCount
										, lastSyncResult
										, lastSyncMessage);
								}
								catch (Exception ex)
								{
								    Console.WriteLine("Destination Server:  {6}\n\rSource Server:  {0}\n\rPartition Name:  {1}\n\rLastSuccessfulSync: {2}\n\rConsecutiveFailure: {3}\n\rLastSyncResult:  {4}\n\rLastSyncMessage:  {5}\n\r"
								    , sourceServer
								    , partitionName
								    , lastSuccessfulSync.ToString()
								    , consecutiveFailureCount.ToString()
								    , lastSyncResult.ToString()
								    , lastSyncMessage
								    , dcri.DCName);
								}

#region Commented out since we shouldn't hit null values since now that it was identified that the reason null values were showing up was due to deleted links
//Can be deleted if above code works, left in for testing                               
/*                                catch (NullReferenceException)
                                {
#if DEBUG
                                    if (string.IsNullOrEmpty(dcri.ErrorMessage))
                                    {
                                        Console.WriteLine("\tError updating replication information for server:  {0}", dcri.DCName);
                                    }
#endif
                                    dcri.ErrorMessage += string.Format("Error updating replication information:  {0}", dcri.DCName);
                                    dcri.ErrorMessage += string.Format("\n\r\tPartition Name:\t{0}", partitionName);
                                    dcri.ErrorMessage += string.Format("\n\r\tSource Server:\t{0}", sourceServer);
                                    dcri.ErrorMessage += string.Format("\n\r\tSource Server InvocationID:\t{0}", sourceServerInvocationID.ToString("B"));
                                    dcri.ErrorMessage += string.Format("\n\r\tLast Success:\t{0}", lastSuccessfulSync.ToString());
                                    dcri.ErrorMessage += string.Format("\n\r\tConsecutive Fail:\t{0}", consecutiveFailureCount.ToString());
                                    dcri.ErrorMessage += string.Format("\n\r\tLast Sync:\t{0}", lastSyncResult.ToString());
                                    dcri.ErrorMessage += string.Format("\n\r\tLast Message:\t{0}", lastSyncMessage);
                                    dcri.ErrorMessage += "\n\r";
                                }
*/
#endregion
							}
						}
						else
						{
							dcri.ErrorMessage = "Request for data from a replication neighbor returned a null value.";
						}
					}
				}
				else
				{
					dcri.ErrorMessage = "Replication partners returned for this naming context is NULL or zero.";
				}
			}
			//These are normal and expected.
			catch (AD.ActiveDirectoryServerDownException e)
			{
				dcri.ErrorMessage = e.Message;
			}
			catch (AD.ActiveDirectoryObjectNotFoundException e)
			{
				dcri.ErrorMessage = e.Message;
			}
			catch (UnauthorizedAccessException e)
			{
				dcri.ErrorMessage = e.Message;
			}
			catch (AD.ActiveDirectoryOperationException e)
			{
				dcri.ErrorMessage = e.Message;
				dcri.StackTrace = e.StackTrace;
			}
			finally
			{
				//Dispose before allowing the main thread to continue
				if (addc != null)
				{
					addc.Dispose();
				}
				TimeSpan waitedNCs = new TimeSpan(0, 0, 0, 0, Environment.TickCount - midTicks);
				TimeSpan waitedTotal = new TimeSpan(0, 0, 0, 0, Environment.TickCount - startTicks);
				dcri.AddDataCollectionTimes(waitedTotal, waitedNCs);
				System.Diagnostics.Debug.WriteLine("\nProcessed Replication Data:  \t" + dcri.DCName + "\t" + waitedNCs.TotalSeconds.ToString("N0") + "." + waitedNCs.Milliseconds.ToString("N0"));
				System.Diagnostics.Debug.WriteLine("\nCompletely Processed:\t" + dcri.DCName + "\t" + waitedTotal.TotalSeconds.ToString("N0") + "." + waitedTotal.Milliseconds.ToString("N0"));

			}
			#endregion
		}

		private void analyzeTopology()
		{
			#region Count instances of each NC.
			foreach (DomainControllerReplicationInfo dcri in _dcs)
			{
				foreach (NamingContextInfo nci in dcri.HostedNcs)
				{
					if (nci.IsWritable)
					{
						if (!_ncInstanceCount.ContainsKey(nci.NCName))
						{
							_ncInstanceCount.Add(nci.NCName, 0);
						}
						_ncInstanceCount[nci.NCName]++;
					}
				}
			}
			#endregion

			#region Analyze each server for failed and missing connections.
			foreach (DomainControllerReplicationInfo dcri in _dcs)
			{
				if (string.IsNullOrEmpty(dcri.ErrorMessage))
				{
					if (!isInboundReplicationStable(dcri))
					{
						_isReplicationTopologyStable = false;
					}
					if (!isOutboundReplicationStable(dcri))
					{
						_isReplicationTopologyStable = false;
					}
				}
				else
				{
					//Critical Error - We can't access all the data we need on a box, so we don't know if it's topology is stable
					addReplicationResultToTable("SRVR_ACCESS"
						, dcri.DCName
						, dcri.SiteName
						, string.Empty
						, null
						, string.Empty
						, null
						, null
						, dcri.ErrorCode
						, dcri.ErrorMessage
						, true);
					_isReplicationTopologyStable = false;
				}
			}
			#endregion

			if (!isSiteReplicatingInboundAndOutbound())
			{
				_isReplicationTopologyStable = false;
			}
		}

		/// <summary>
		/// Business logic to determine if there are any errors on the inbound connections
		/// </summary>
		/// <param name="destinationDC">DomainControllerReplicationInfo object to analyze</param>
		/// <returns>A boolean indicating whether or not there are any errors on the inbound connections</returns>
		private bool isInboundReplicationStable(DomainControllerReplicationInfo dcri)
		{
			bool hasStabilityErrors = false;

			foreach (NamingContextInfo nci in dcri.HostedNcs)
			{
				int countLinksNeverSucceededIn = 0, countLinksNeverSucceededInDueTo8606 = 0;

				//Check inbound replication
				if (nci.ReplicationConnections.Count > 0)
				{
					bool hasReplicatedAtLeastOnce = false;
					//Check to ensure the last success time is not a null value.
					foreach (ReplicationInfo ri in nci.ReplicationConnections)
					{
						//If it errored out
						if (DateTime.Compare(ri.LastSuccessfulSync.ToUniversalTime(), new DateTime(1601, 1, 1, 0, 0, 0, DateTimeKind.Utc).ToUniversalTime()) == 0)
						{
							//Non-Critical Error - Though this has never succeeded, it doesn't mean the others have failed.
							addReplicationResultToTable("LINK_NEVER_SUCCEEDED_IN"
								, dcri.DCName
								, dcri.SiteName
								, nci.NCName
								, nci.IsWritable
								, ri.SourceServer
								, null
								, ri.ConsecutiveFailureCount
								, ri.LastSyncResultCode
								, ri.LastSyncResultMessage
								, false);
							countLinksNeverSucceededIn++;

							if (ri.LastSyncResultCode == 8606)
							{
								countLinksNeverSucceededInDueTo8606++;
							}
						}
						//Check to ensure each and every inbound connection has successfully completed.
						else if (ri.LastSyncResultCode > 0)
						{
							//Non-Critical Error - Though this link is failing to replicate, it doesn't mean the others are failing.
							addReplicationResultToTable("LINK_FAILURE"
								, dcri.DCName
								, dcri.SiteName
								, nci.NCName
								, nci.IsWritable
								, ri.SourceServer
								, ri.LastSuccessfulSync
								, ri.ConsecutiveFailureCount
								, ri.LastSyncResultCode
								, ri.LastSyncResultMessage
								, false);
							hasReplicatedAtLeastOnce = true;
						}
						else
						{
							hasReplicatedAtLeastOnce = true;
						}
					}

					//Throw an error if it never ever completed an inbound replication cycle.
					if (!hasReplicatedAtLeastOnce)
					{
						//Critical - We don't know if and when this box ever talked to the rest of the forest.  This is bad, because it could be greater than TSL.
						addReplicationResultToTable("NC_NEVER_COMPLETED_INBOUND"
							, dcri.DCName
							, dcri.SiteName
							, nci.NCName
							, nci.IsWritable
							, string.Empty
							, null
							, null
							, null
							, string.Empty
							, true);
						hasStabilityErrors = true;

						//If replication has never succeeded because it is being blocked due lingering objects existence, 
						//ignore this as a critical failure.  Since the goal is to automate fixing LOs, critically failing the topology health
						//check works against that goal.  So we are flagging these scenarios as not critical failures.
						if (countLinksNeverSucceededIn == countLinksNeverSucceededInDueTo8606)
						{
							_isReplicationTopologyUnstableDueToLingeringObjects = true;
						}
					}
				}
				//NC has no inbound replication connections
				else
				{
					//Check to ensure that the NC is not the only copy of the NC in the forest.
					if (_ncInstanceCount.ContainsKey(nci.NCName))
					{
						if (_ncInstanceCount[nci.NCName] < 2)
						{
							//Non-Critical - While it may be an issue if there is only one copy of an NC from a redundancy stand point
							addReplicationResultToTable("SINGLE_WRITABLE_INSTANCE_OF_NC"
								, dcri.DCName
								, dcri.SiteName
								, nci.NCName
								, nci.IsWritable
								, string.Empty
								, null
								, null
								, null
								, "This is only a concern for redundancy."
								, false);
						}
						else
						{
							//Critical - No inbound links = no inbound data = bad.
							addReplicationResultToTable("MISSING_INBOUND_REPL"
								, dcri.DCName
								, dcri.SiteName
								, nci.NCName
								, nci.IsWritable
								, string.Empty
								, null
								, null
								, null
								, string.Empty
								, true);
							hasStabilityErrors = true;
						}
					}
					else
					{
						//Critical - This error isn't a problem so much as it indicates that the configuration NC is significantly divergent
						//This means an NC has been removed and hasn't replicated to this box yet
						addReplicationResultToTable("NO_WRITABLE_INSTANCES_OF_THIS_PARTITION"
							, dcri.DCName
							, dcri.SiteName
							, nci.NCName
							, nci.IsWritable
							, string.Empty
							, null
							, null
							, null
							, "The configuration naming context has not converged with the rest of the forest."
							, true);
						hasStabilityErrors = true;
					}
				}
			}

			return !hasStabilityErrors;
		}

		/// <summary>
		/// Business logic to determine if there are any errors on the inbound connections
		/// </summary>
		/// <param name="destinationDC">DomainControllerReplicationInfo object to analyze</param>
		/// <returns>A boolean indicating whether or not there are any errors on the outbound connections</returns>
		private bool isOutboundReplicationStable(DomainControllerReplicationInfo destinationDC)
		{
			bool hasStabilityErrors = false;

			foreach (NamingContextInfo destinationNamingContext in destinationDC.HostedNcs)
			{
				int countLinksNeverSucceededOut = 0, countLinksNeverSucceededOutDueTo8606 = 0;
				bool hasOutbound = false, hasSucceededAtLeastOnce = false;
				//Look through all the other DCs to find out which ones pull from this one.
				foreach (DomainControllerReplicationInfo sourceDc in _dcs)
				{
					if ((sourceDc.Contains(destinationNamingContext.NCName)) && (string.Compare(destinationDC.DCName, sourceDc.DCName, true) != 0))
					{
						//If we find any other DC that replicates this NC from this NC in, there are outbound connections.
						//Yes, I could have found this via the outbound connection property of the DomainController class,
						//but that doesn't tell me if I am replicating to a writable or not as I want to ensure that for a
						//writable NC there is an outbound writable partner.
						ReplicationInfo ri = sourceDc[destinationNamingContext.NCName].GetReplicationStateToServer(destinationDC.DCName);
						if (ri != null)
						{
							hasOutbound = true;
							//Check to make sure the outbound connection has successfully replicated at least once
							if (DateTime.Compare(ri.LastSuccessfulSync.ToUniversalTime(), new DateTime(1601, 1, 1, 0, 0, 0, DateTimeKind.Utc).ToUniversalTime()) != 0)
							{
								hasSucceededAtLeastOnce = true;
								countLinksNeverSucceededOut++;

								if (ri.LastSyncResultCode == 8606)
								{
									countLinksNeverSucceededOutDueTo8606++;
								}
							}
						}
					}
				}

				//If it is writable,  doesn't have an outbound connection, and there is more than one instance of the NC in the forest
				if ((!hasOutbound)
					&& (destinationNamingContext.IsWritable)
					&& (_ncInstanceCount[destinationNamingContext.NCName] > 2)	//Only flag this once.  See checking inbound replication.
					)
				{
					//Critcal - It not share its data with anyone else
					addReplicationResultToTable("MISSING_OUTBOUND_REPL"
						, destinationDC.DCName
						, destinationDC.SiteName
						, destinationNamingContext.NCName
						, destinationNamingContext.IsWritable
						, string.Empty
						, null
						, null
						, null
						, "No writable replication peers are getting the data on this box"
						, true);
					hasStabilityErrors = true;
				}
				//If it has not succeeded at least once, is writable, and has partners replicating outbound.
				else if ((!hasSucceededAtLeastOnce)
					&& (destinationNamingContext.IsWritable)
					&& (hasOutbound)
					)
				{
					//Critical - It not share its data with anyone else
					addReplicationResultToTable("NC_NEVER_COMPLETED_OUTBOUND"
						, destinationDC.DCName
						, destinationDC.SiteName
						, destinationNamingContext.NCName
						, destinationNamingContext.IsWritable
						, string.Empty
						, null
						, null
						, null
						, string.Empty
						, true);
					hasStabilityErrors = true;

					//If replication has never succeeded because it is being blocked due lingering objects existence, 
					//ignore this as a critical failure.  Since the goal is to automate fixing LOs, critically failing the topology health
					//check works against that goal.  So we are flagging these scenarios as not critical failures.
					if (countLinksNeverSucceededOut == countLinksNeverSucceededOutDueTo8606)
					{
						_isReplicationTopologyUnstableDueToLingeringObjects = true;
					}
				}
				else
				{
					//Do nothing since
					//We don't care if there are outbound connections from a RO partition since changes can't originate in an RO partition.
					//If there are and they are failing we catch it under the failing links on the inbound direction.
				}
			}

			return !hasStabilityErrors;
		}

		/// <summary>
		/// Iterate through all the sites that have DCs and ensure there is at least one inbound connection from out of site for each NC.
		/// </summary>
		/// <returns>False if the site is missing inbound or outbound connection</returns>
		private bool isSiteReplicatingInboundAndOutbound()
		{
			bool hasStabilityErrors = false;

			//Build data structures to track the list of sites and whether or not there is a writable copy of the NC in the site.
			Sites siteTopology = new Sites();
			foreach (DomainControllerReplicationInfo dcri in _dcs)
			{
				foreach (NamingContextInfo nci in dcri.HostedNcs)
				{
					if (!siteTopology.Contains(dcri.SiteName, nci.NCName))
					{
						siteTopology.Add(dcri.SiteName, nci.NCName);
					}

					if (dcri[nci.NCName].IsWritable)
					{
						siteTopology[dcri.SiteName, nci.NCName].HasAWritableInstance = true;
					}
					siteTopology[dcri.SiteName, nci.NCName].NumberOfInstances++;
				}
			}

			Dictionary<string, int> trackNumberOfSitesHostingNC = new Dictionary<string, int>();
			Dictionary<string, int> trackNumberOfSitesHostingWritableInstanceOfNC = new Dictionary<string, int>();
			foreach (SiteNCInfo sni in siteTopology)
			{
				if (!trackNumberOfSitesHostingNC.ContainsKey(sni.NamingContext))
				{
					trackNumberOfSitesHostingNC.Add(sni.NamingContext, 0);
					//If it tain't in the first, it shouldn't be in the second.
					if (trackNumberOfSitesHostingWritableInstanceOfNC.ContainsKey(sni.NamingContext))
					{
						throw new ApplicationException("Error processing site topology.  NC already exists as writable");
					}
					trackNumberOfSitesHostingWritableInstanceOfNC.Add(sni.NamingContext, 0);
				}
				trackNumberOfSitesHostingNC[sni.NamingContext]++;
				if (sni.HasAWritableInstance)
				{
					trackNumberOfSitesHostingWritableInstanceOfNC[sni.NamingContext]++;
				}
			}
			//Iterate through the list of site information and then check each site to make sure there are inbound connections
			foreach (SiteNCInfo sni in siteTopology)
			{
				//Save processing time by only worrying about sites with more than one instance of the NC
				//	the missinging inbound/outbound connections on the per server basis is checked under
				//	isInboundReplicationStable() or isOutboundReplicationStable()
				//  Also, only worry about NCs which exist in more than one site
				if ((sni.NumberOfInstances > 1) && (trackNumberOfSitesHostingNC[sni.NamingContext] > 1))
				{
					foreach (DomainControllerReplicationInfo destinationDC in _dcs)
					{
						//if this DC hosts this NC
						NamingContextInfo destinationNci = destinationDC[sni.NamingContext];
						if (destinationNci != null)
						{
							//Iterate through all the replicaiton links
							foreach (ReplicationInfo destinationDcReplInfo in destinationNci.ReplicationConnections)
							{
								//Since there isn't an elegent way to pull the dc from the list of DCs, iterate
								//	through all of them and process if the names match
								foreach (DomainControllerReplicationInfo sourceDC in _dcs)
								{
									if ((string.Compare(destinationDcReplInfo.SourceServer, sourceDC.DCName, true) == 0))
									{
										//now that we know we are looking at the DC in the replication link, check to see if said DC hosts the NC
										NamingContextInfo inboundNci = sourceDC[sni.NamingContext];
										//Also, if this site has a writable copy of the NC AND the writable NC exists
										//	in more than one site, there must be an connection to another
										//	copy of the writable NC.  Don't worry otherwise.
										if ((inboundNci != null)
											&& ((sni.HasAWritableInstance && inboundNci.IsWritable) || (!sni.HasAWritableInstance)))
										{
											//If the destination DC is in the site and the source DC to is in another site
											if ((string.Compare(sni.SiteName, destinationDC.SiteName) == 0)
												&& (string.Compare(sni.SiteName, sourceDC.SiteName, true) != 0)
												)
											{
												siteTopology[sni.SiteName, destinationNci.NCName].HasInbound = true;
											}
											//the destination dc isn't in the site and the source DC is.
											else if ((string.Compare(sni.SiteName, destinationDC.SiteName) != 0)
												&& (string.Compare(sni.SiteName, sourceDC.SiteName, true) == 0))
											{
												siteTopology[sni.SiteName, destinationNci.NCName].HasOutbound = true;
											}
										}
									}
								}
							}
						}
					}
				}
			}

			foreach (SiteNCInfo sni in siteTopology)
			{
				if ((sni.NumberOfInstances > 1) && (trackNumberOfSitesHostingNC[sni.NamingContext] > 1))
				{
					if ((!sni.HasInbound)
						&& !(trackNumberOfSitesHostingWritableInstanceOfNC[sni.NamingContext] <= 1)
						)
					{
						//
						addReplicationResultToTable("SITE_MISSING_INBOUND"
							, string.Empty
							, sni.SiteName
							, sni.NamingContext
							, sni.HasAWritableInstance
							, string.Empty
							, null
							, null
							, null
							, string.Empty
							, true);
						hasStabilityErrors = true;
					}

					//If the site does not have outbound connections, 
					//	contains a writable (we can't originate changes in RO NCs, 
					//	except LO reanimations whcih don't count in this case),
					//	and more than one site contains a writable copy.
					if ((!sni.HasOutbound)
						&& (sni.HasAWritableInstance)
						&& (trackNumberOfSitesHostingNC[sni.NamingContext] > 1)
						&& (trackNumberOfSitesHostingWritableInstanceOfNC[sni.NamingContext] > 1)
						)
					{
						//Critical - Well now aren't we selfish?  Not sharing our data with anyone else.
						addReplicationResultToTable("SITE_MISSING_OUTBOUND"
							, string.Empty
							, sni.SiteName
							, sni.NamingContext
							, sni.HasAWritableInstance
							, string.Empty
							, null
							, null
							, null
							, string.Empty
							, true);
						hasStabilityErrors = true;
					}
				}
				else if (trackNumberOfSitesHostingNC[sni.NamingContext] == 1)
				{
					addReplicationResultToTable("NC_EXISTS_IN_ONE_SITE_ONLY"
						, string.Empty
						, sni.SiteName
						, sni.NamingContext
						, sni.HasAWritableInstance
						, string.Empty
						, null
						, null
						, null
						, "This is only a concern for redundancy."
						, false);
				}
			}

			//TODO:  Make sure we don't flag as no in/out if the NC exists in only one site.
			return !hasStabilityErrors;
		}

		private void addReplicationResultToTable(
			string failureType
			, string servername
			, string sitename
			, string ncName
			, bool? isWritable
			, string sourceDC
			, DateTime? lastSuccessfulSync
			, int? numFailures
			, int? errorCode
			, string errorMessage
			, bool isCritical
			)
		{
			DataRow nr = _replicationTopologyStateData.NewRow();
			nr[COLUMN_NAME_FAIL_TYPE] = failureType;
			nr[COLUMN_NAME_DESTINATION_DC] = servername;
			nr[COLUMN_NAME_SITE_NAME] = sitename;
			nr[COLUMN_NAME_NAMING_CONTEXT] = ncName;
			nr[COLUMN_NAME_ERROR_MESSAGE] = errorMessage;
			nr[COLUMN_NAME_IS_CRITICAL] = isCritical;
			nr[COLUMN_NAME_SOURCE_DC] = sourceDC;

			if (isWritable.HasValue)
			{
				nr[COLUMN_NAME_IS_WRITABLE] = isWritable;
			}

			if (lastSuccessfulSync.HasValue)
			{
				nr[COLUMN_NAME_LAST_SUCCESSFUL_SYNC] = lastSuccessfulSync;
			}

			if (numFailures.HasValue)
			{
				nr[COLUMN_NAME_NUMBER_OF_SYNC_FAILURE_ATTEMPTS] = numFailures;
			}

			if (errorCode.HasValue)
			{
				nr[COLUMN_NAME_ERROR_CODE] = errorCode;
			}

			_replicationTopologyStateData.Rows.Add(nr);
		}
	}
}
